﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Drawing.Text;
using System.Drawing.Drawing2D;

namespace TurtleLib.Simulation
{
  public class DragablePanel : IPanel
  {
    public class ResizingArgs
    {
      public bool Expanding { get; private set; }
      public bool Cancel { get; set; }

      public ResizingArgs(bool expand)
      {
        Expanding = expand;
      }
    }

    public static float DEFAULT_FONT_SIZE = 14f;

    public event Action<DragablePanel> Activated;

    protected System.Windows.Forms.Panel _self;
    protected WorldUx _parent;
    protected bool _activated;
    protected string _name;
    protected string _statusMessage;
    protected List<IChildPanel> _children;
    private bool _expanded;
    protected DragComponent _dragger;

    public bool IsActive { get { return _activated; } }
    public Point Location { get { return _self.Location; } set { _self.Location = value; } }
    public Color BackColor { get { return _self.BackColor; } set { _self.BackColor = value; } }
    public int Width { get { return _self.Width; } }
    public int Height { get { return _self.Height; } }
    public int Left { get { return _self.Left; } }
    public int Right { get { return _self.Right; } }
    public int Bottom { get { return _self.Bottom; } }
    public int Top { get { return _self.Top; } }
    public Rectangle Bounds { get { return _self.Bounds; } }
    public Control Handle { get { return _self; } }
    public Size Size { get { return _self.Size; } set { _self.Size = value; } }
    public bool Visible { get { return _self.Visible; } }
    public World World { get { return _parent.World; } }

    internal class CustomPanel : Panel
    {
      private DragablePanel _target;

      public CustomPanel(DragablePanel target)
      {
        _target = target;
        SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
      }

      public DragablePanel Target { get { return _target; } }
    }

    public DragablePanel(WorldUx world)
    {
      // I can't help it, I spend most of my time working in C++
      _name = string.Empty;
      _statusMessage = string.Empty;

      _children = new List<IChildPanel>();

      _self = new CustomPanel(this);
      _self.Paint += OnPaint;
      _self.MouseDoubleClick += OnDoubleClick;

      _dragger = new DragComponent();
      _dragger.SubscribeTo(_self);
      _dragger.Drag += size => Drag(size.Width, size.Height);
      _dragger.Captured += () => Activated(this);

      Size = GetMinimumSize();

      _parent = world;
      Activated += delegate { _activated = true; _self.Invalidate(); };
    }

    protected Size GetMinimumSize()
    {
      return new Size(200, 30);
    }

    protected Size GetPreferredSize()
    {
      // if smaller than minimum child size, expand
      var max = GetMinimumSize();

      foreach (var child in _children)
      {
        Rectangle childPreferredBounds = child.Bounds;
        max = new Size(Math.Max(max.Width, childPreferredBounds.Right + 2), Math.Max(max.Height, childPreferredBounds.Bottom));
      }

      return max;
    }

    protected void OnDoubleClick(object sender, MouseEventArgs e)
    {
      ToggleCurrentExpansion();
      RunExpansion();
      Invalidate();
    }

    private void ToggleCurrentExpansion()
    {
      // if smaller than minimum child size, expand
      var max = GetPreferredSize();

      // grow to preferred size
      if (Size.Width < max.Width || Size.Height < max.Height)
      {
        _expanded = true;
      }
      else
      {
        _expanded = false;
      }
    }

    private void RunExpansion()
    {
      if (_expanded)
      {
        var max = GetPreferredSize();

        var oldSize = Size;
        if (TryToSize(max))
        {
          Size = max;
        }
        else
        {
          // if size didn't even change we were fully blocked
          // let's go minimum size
          if (Size.Width == oldSize.Width && Size.Height == oldSize.Height)
          {
            Size = GetMinimumSize();
            _expanded = false;
          }
        }
      }
      else
      {
        Size = GetMinimumSize();
      }
    }

    public DragResult Drag(int dx, int dy)
    {
      int nextX = Location.X + dx;
      int nextY = Location.Y + dy;

      // normalize
      var next = new Point(nextX, nextY);

      if (next == Location)
        return DragResult.Success; // no movement requested

      var oldLoc = Location;
      Location = _parent.GetPointEdge(this, next);

      if (Location == next)
        return DragResult.Success;
      else if (oldLoc == Location)
        return DragResult.Blocked;
      else if (oldLoc.X == Location.X || oldLoc.Y == Location.Y) // only one side blocked
        return DragResult.PushedAndBlocked;
      else
        return DragResult.Pushed; // both sides pushed
    }

    public void Select(bool selected)
    {
      if (_activated == selected)
        return;

      _activated = selected;

      if (!_activated)
      {
        _dragger.ReleaseCurrentDrag();
      }

      _self.Invalidate();
    }

    protected void OnPaint(object sender, PaintEventArgs e)
    {
      e.Graphics.DrawRectangle(new Pen(IsActive ? Color.Yellow : _parent.BackColor, 2.0f), new Rectangle(Point.Empty, new Size(Width, Height)));

      foreach (var child in _children)
      {
        if (e.ClipRectangle.IntersectsWith(child.Bounds))
          child.Render(e.Graphics);
      }

      string fullTitle = _name ?? string.Empty;
      if (!string.IsNullOrWhiteSpace(_statusMessage))
      {
        fullTitle += " (" + (_statusMessage ?? string.Empty) + ")";
      }
      WriteColoredText(e.Graphics, fullTitle, 13f, Point.Empty, Color.Black, BackColor);
    }

    private void WriteColoredText(Graphics g, string text, float size, Point origin, Color fontColor, Color backColor)
    {
      if (string.IsNullOrEmpty(text))
        return;

      var installedFonts = new InstalledFontCollection();
      var fontFamily = installedFonts.Families.FirstOrDefault(fam => fam.Name.ToLower().Contains("consolas"));
      var font = new Font(fontFamily.Name, DEFAULT_FONT_SIZE, FontStyle.Regular);

      Brush fontBrush = new SolidBrush(fontColor);
      Brush backBrush = new SolidBrush(backColor);

      g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
      g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

      StringFormat format = new StringFormat();
      GraphicsPath gpath = new GraphicsPath();

      var fontAlign = origin;
      fontAlign.Offset(-2, 0);
      gpath.AddString(text, font.FontFamily, (int)font.Style, font.Size, fontAlign, format);

      g.FillPath(fontBrush, gpath);

      backBrush.Dispose();
      fontBrush.Dispose();
      gpath.Dispose();
    }

    public void Show()
    {
      _self.Show();
    }


    public string Title
    {
      get
      {
        return _name;
      }
      set
      {
        _name = value;
        _self.Invalidate();
      }
    }

    public string StatusMessage
    {
      get
      {
        return _statusMessage;
      }
      set
      {
        _statusMessage = value;
        _self.Invalidate();
      }
    }

    public void Expand(bool expand, int rate)
    {
      var args = new ResizingArgs(expand);

      var max = GetMinimumSize();
      var successfulResizing = new List<IChildPanel>();

      foreach (var child in _children)
      {
        Rectangle childPreferredSize = child.Resize(args);

        if (args.Cancel)
          break;

        successfulResizing.Add(child);

        max = new Size(Math.Max(max.Width, childPreferredSize.Right), Math.Max(max.Height, childPreferredSize.Bottom));
      }

      if (!TryToSize(max))
      {
        var reverse = new ResizingArgs(!expand);

        successfulResizing.ForEach(child => child.Resize(reverse));
        // force reverse on all, even if one fails
        reverse.Cancel = false;
      }

      Invalidate();
    }

    protected bool TryToSize(Size goalSize)
    {
      Size = _parent.GetSizeEdge(this, goalSize);

      if (Width < goalSize.Width || Height < goalSize.Height)
      {
        int fx = goalSize.Width;
        int fy = goalSize.Height;
        int tdx = 0;
        int tdy = 0;

        while (true)
        {
          int dx = Width < fx ? 1 : 0;
          int dy = Height < fy ? 1 : 0;

          if (dx == 0 && dy == 0)
            break;

          if (!(Poke(dx, 0) && Poke(0, dy) && Poke(dx, dy)))
          {
            return false;
          }

          tdx += dx;
          tdy += dy;

          Size = new Size(Width + dx, Height + dy);
        }
      }
      else if (Width > goalSize.Width || Height > goalSize.Height)
      {
        Size = goalSize;
      }

      return true;
    }

    public bool Poke(int dx, int dy)
    {
      if (dx == 0 && dy == 0)
        return true;

      var oldLoc = Location;
      var dr = Drag(dx, dy);
      bool good = dr != DragResult.PushedAndBlocked && dr != DragResult.Blocked;

      if (!good)
      {
        Location = oldLoc;
        dr = Drag(-dx, -dy);
      }
      else
        Drag(-dx, -dy);

      return dr != DragResult.PushedAndBlocked && dr != DragResult.Blocked;
    }

    public void Show(bool show)
    {
      if (_self.InvokeRequired)
      {
        _self.Invoke((Action)(() => Show(show)));
        return;
      }

      if (show)
        _self.Show();
      else
        _self.Hide();
    }

    public void OnMouseWheel(object sender, MouseEventArgs args)
    {
      _parent.OnMouseWheel(sender, args);
    }

    public void AddControl(Control control)
    {
      _self.Controls.Add(control);
    }

    public void RemoveControl(Control control)
    {
      _self.Controls.Remove(control);
    }

    public void Invalidate()
    {
      _self.Invalidate();
    }

    public Rectangle GetChildRenderArea()
    {
      return new Rectangle(2, 30, Width -4, Height - 32);
    }

    public void AddChildPanel(ChildPanel child)
    {
      child.Location = GetChildRenderArea().Location;
      _children.Add(child);

      child.MouseDoubleClick += OnDoubleClick;
      _dragger.SubscribeTo(child);
    }

    public bool Expanded
    {
      get
      {
        return _expanded;
      }
      set
      {
        if (_expanded == value)
          return; // do nothing

        _expanded = value;
        if (Visible)
        {
          if (_self.InvokeRequired)
          {
            _self.Invoke((Action)RunExpansion);
            return;
          }
          RunExpansion();
        }
      }
    }

    public void Update()
    {
      RunExpansion();
    }
  }
}
